package org.jdemo.orm.mybatis.support.mybatis;

import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.builder.xml.XMLStatementBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.jdemo.orm.mybatis.support.BaseDAO;
import org.jdemo.util.CollectionUtil;
import org.jdemo.util.IOUtil;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * MybatisLoader
 *
 * @author longlin(longlin@cyou-inc.com)
 * @date 202014/4/16
 * @since V1.0
 */
public class MybatisLoader<T> {
    private Configuration configuration;

    private Class<T> entityClass;
    private String entityName;

    private Map<String, String> columnPropertyMap;
    private boolean autoIncrement;
    private String idProperty;
    private String idColumn;
    private XPathParser extendMapperParser;

    public MybatisLoader(Configuration configuration, Class<T> entityClass, String entityName, boolean autoIncrement,
                         Map<String, String> columnPropertyMap, String idProperty, String idColumn) {
        this.configuration = configuration;
        this.entityClass = entityClass;
        this.entityName = entityName;
        this.columnPropertyMap = columnPropertyMap;
        this.autoIncrement = autoIncrement;
        this.idProperty = idProperty;
        this.idColumn = idColumn;

        String extendMapperContent = replaceExtendMapper();
        InputStream extendInputStream = new ByteArrayInputStream(extendMapperContent.getBytes());
        this.extendMapperParser = new XPathParser(extendInputStream, true, configuration.getVariables(), new XMLMapperEntityResolver());
    }

    /**
     * 设置类别名，等价于mybatis-config.xml中配置：<typeAlias type="org.jdemo.orm.mybatis.entity.Entity" alias="Entity"/>
     */
    public void loadAlias() {
        configuration.getTypeAliasRegistry().registerAlias(this.entityName.toLowerCase(), this.entityClass);
    }

    /**
     * 设置ResultMap，等价于EntityMapper.xml文件中的配置：<resultMap id="EntityResult" type="Entity">
     */
    public void loadResultMap() {
        Map<String, Field> entityProperties = new HashMap<String, Field>();
        try {
            for (String property : this.columnPropertyMap.values()) {
                entityProperties.put(property, this.entityClass.getDeclaredField(property));
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        final TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry();
        List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
        for (String column : this.columnPropertyMap.keySet()) {
            String property = this.columnPropertyMap.get(column);
            resultMappings.add(new ResultMapping.Builder(configuration, property, column, registry.getTypeHandler(entityProperties.get(property).getType())).build());
        }
        ResultMap resultMap = new ResultMap.Builder(configuration, this.entityName + "." + this.entityName + "Result", this.entityClass, resultMappings).build();
        configuration.addResultMap(resultMap);
    }

    /**
     * 设置Mapper.xml文件路径，等价于mybatis-config.xml中配置：<mapper resource="org/jdemo/orm/mybatis/entity/EntityMapper.xml" />
     * @throws java.io.IOException
     */
    public void loadEntityMapper(String mapperResource) {
        ErrorContext.instance().resource(mapperResource);
        InputStream mapperInputStream = null;
        try {
            mapperInputStream = Resources.getResourceAsStream(mapperResource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        XMLMapperBuilder mapperParser = new XMLMapperBuilder(mapperInputStream, configuration, mapperResource, configuration.getSqlFragments());
        mapperParser.parse();
    }

    /**
     * 载入mybatis-mapper.xml
     * @return
     */
    private String replaceExtendMapper() {
        String extendMapperContent = IOUtil.readFile(BaseDAO.class.getResourceAsStream(MybatisContext.EXTEND_MAPPER), "utf-8");

        List<String> replaceColumns = new ArrayList<String>();
        for (String column : this.columnPropertyMap.keySet()) {
            if (!this.autoIncrement || !column.equals(idColumn)) {
                replaceColumns.add(column);
            }
        }
        List<String> replaceProperties = new ArrayList<String>();
        for (String property : this.columnPropertyMap.values()) {
            if (!this.autoIncrement || !property.equals(idProperty)) {
                replaceProperties.add("#{entity." + property + "}");
            }
        }

        String idPropertyType = "long";
        try {
            Field idPropertyField = entityClass.getDeclaredField(idProperty);
            if (idPropertyField.getType() == Integer.class) {
                idPropertyType = "int";
            }
        } catch (NoSuchFieldException ignored) {}

        extendMapperContent = extendMapperContent.replaceAll("%\\{entityName}", this.entityName);
        extendMapperContent = extendMapperContent.replaceAll("%\\{tableColumns}", CollectionUtil.join(replaceColumns, ","));
        extendMapperContent = extendMapperContent.replaceAll("%\\{entityProperties}", CollectionUtil.join(replaceProperties, ","));
        extendMapperContent = extendMapperContent.replaceAll("%\\{idProperty}", idProperty);
        extendMapperContent = extendMapperContent.replaceAll("%\\{idPropertyType}", idPropertyType);
        extendMapperContent = extendMapperContent.replaceAll("%\\{idColumn}", idColumn);

        if (!this.autoIncrement) {
            extendMapperContent = extendMapperContent.replaceAll("<selectKey[\\s\\S]*selectKey>", "");
        }
        return extendMapperContent;
    }

    public void loadExtendFragment(String mapperResource) {
        List<XNode> list = extendMapperParser.evalNodes("/mapper/sql");
        for (XNode xNode : list) {
            this.configuration.getSqlFragments().put(this.entityName + "." + xNode.getStringAttribute("id"), xNode);
        }
    }

    /**
     * 载入通用的mapper配置文件
     */
    public void loadExtendStatement(String mapperResource) {
        List<XNode> list = extendMapperParser.evalNodes("/mapper/select|/mapper/insert|/mapper/update|/mapper/delete");
        for (XNode xNode : list) {
            MapperBuilderAssistant mapperBuilderAssistant = new MapperBuilderAssistant(configuration, mapperResource);
            mapperBuilderAssistant.setCurrentNamespace(this.entityClass.getSimpleName());
            XMLStatementBuilder xmlStatementBuilder = new XMLStatementBuilder(configuration, mapperBuilderAssistant, xNode);
            xmlStatementBuilder.parseStatementNode();
        }
    }

}
